package com.dbflow5.config;

import com.dbflow5.StringUtils;
import com.dbflow5.database.DatabaseCallback;
import com.dbflow5.database.OpenHelper;
import com.dbflow5.runtime.ModelNotifier;
import com.dbflow5.transaction.BaseTransactionManager;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;


/**
 * Description:
 */
public class DatabaseConfig {
    public Class<?> databaseClass;
    public OpenHelperCreator openHelperCreator;
    public TransactionManagerCreator transactionManagerCreator;
    public DatabaseCallback callback;
    public Map<Class<?>, TableConfig<?>> tableConfigMap;
    public ModelNotifier modelNotifier;
    public boolean isInMemory;
    public String databaseName;
    public String databaseExtensionName;
    public DBFlowDatabase.JournalMode journalMode;

    public DatabaseConfig(Class<?> databaseClass,
                          OpenHelperCreator openHelperCreator,
                          TransactionManagerCreator transactionManagerCreator,
                          DatabaseCallback callback,
                          Map<Class<?>, TableConfig<?>> tableConfigMap,
                          ModelNotifier modelNotifier,
                          boolean isInMemory,
                          String databaseName,
                          String databaseExtensionName,
                          DBFlowDatabase.JournalMode journalMode) {
        this.databaseClass = databaseClass;
        this.openHelperCreator = openHelperCreator;
        this.transactionManagerCreator = transactionManagerCreator;
        this.callback = callback;
        this.tableConfigMap = tableConfigMap;
        this.modelNotifier = modelNotifier;
        this.isInMemory = isInMemory;
        this.databaseName = databaseName;
        this.databaseExtensionName = databaseExtensionName;
        this.journalMode = journalMode;
    }

    public DatabaseConfig(Builder builder) {
        this.databaseClass = builder.databaseClass;
        this.openHelperCreator = builder.openHelperCreator;
        this.transactionManagerCreator = builder.transactionManagerCreator;
        this.callback = builder.callback;
        this.tableConfigMap = builder.tableConfigMap;
        this.modelNotifier = builder.modelNotifier;
        this.isInMemory = builder.inMemory;
        this.databaseName = getDatabaseName(builder);
        this.databaseExtensionName = getDatabaseExtensionName(builder);
        this.journalMode = builder.journalMode;
        if (!isValidDatabaseName(databaseName)) {
            throw new IllegalArgumentException("Invalid database name " + databaseName + " found. Names must follow " +
                    "the [A-Za-z_]+[a-zA-Z0-9_] pattern.");
        }
    }

    private String getDatabaseName(Builder builder) {
        String databaseName;
        if (builder.databaseName != null) {
            databaseName = builder.databaseName;
        } else {
            databaseName = builder.databaseClass.getSimpleName();
        }
        return databaseName;
    }

    private String getDatabaseExtensionName(Builder builder) {
        String databaseExtensionName;
        if (builder.databaseExtensionName == null) {
            databaseExtensionName = ".db";
        } else if (StringUtils.isNotNullOrEmpty(builder.databaseExtensionName)) {
            databaseExtensionName = "." + builder.databaseExtensionName;
        } else {
            databaseExtensionName = "";
        }
        return databaseExtensionName;
    }

    public <T> TableConfig<T> getTableConfigForTable(Class<T> modelClass) {
        return (TableConfig<T>) tableConfigMap.get(modelClass);
    }


    public static Builder builder(Class<?> database, OpenHelperCreator openHelperCreator) {
        return new Builder(database, openHelperCreator);
    }

    public static Builder inMemoryBuilder(Class<?> database, OpenHelperCreator openHelperCreator) {
        return new Builder(database, openHelperCreator).inMemory();
    }

    /**
     * Checks if databaseName is valid. It will check if databaseName matches regex pattern
     * [A-Za-z_$]+[a-zA-Z0-9_$]
     * Examples:
     * database - valid
     * DbFlow1 - valid
     * database.db - invalid (contains a dot)
     * 1database - invalid (starts with a number)
     *
     * @param databaseName database name to validate.
     * @return `true` if parameter is a valid database name, `false` otherwise.
     */
    private static boolean isValidDatabaseName(String databaseName) {
        Pattern javaClassNamePattern = Pattern.compile("[A-Za-z_$]+[a-zA-Z0-9_$]*");
        return javaClassNamePattern.matcher(databaseName).matches();
    }

    /**
     * Build compatibility class for Java. Use the [DatabaseConfig] class directly if Kotlin consumer.
     */
    public static class Builder {

        Class<?> databaseClass;
        OpenHelperCreator openHelperCreator;

        TransactionManagerCreator transactionManagerCreator = null;
        DatabaseCallback callback = null;
        Map<Class<?>, TableConfig<?>> tableConfigMap = new HashMap<>();
        ModelNotifier modelNotifier = null;
        boolean inMemory = false;
        String databaseName = null;
        String databaseExtensionName = null;
        DBFlowDatabase.JournalMode journalMode = DBFlowDatabase.JournalMode.Automatic;

        public Builder(Class<?> databaseClass, OpenHelperCreator openHelperCreator) {
            this.databaseClass = databaseClass;
            this.openHelperCreator = openHelperCreator;
        }

        public Builder transactionManagerCreator(TransactionManagerCreator creator) {
            this.transactionManagerCreator = creator;
            return this;
        }

        public Builder helperListener(DatabaseCallback callback) {
            this.callback = callback;
            return this;
        }

        public Builder addTableConfig(TableConfig<?> tableConfig) {
            tableConfigMap.put(tableConfig.tableClass, tableConfig);
            return this;
        }

        public <T> Builder table(Class<T> clazz, Function<TableConfig.Builder<T>, Void> fn) {
            TableConfig.Builder<T> builder = TableConfig.builder(clazz);
            fn.apply(builder);
            return addTableConfig(builder.build());
        }

        public Builder modelNotifier(ModelNotifier modelNotifier) {
            this.modelNotifier = modelNotifier;
            return this;
        }

        public Builder inMemory() {
            inMemory = true;
            return this;
        }

        public Builder journalMode(DBFlowDatabase.JournalMode journalMode) {
            this.journalMode = journalMode;
            return this;
        }

        /**
         * databaseName
         *
         * @param name name
         * @return Pass in dynamic database name here. Otherwise it defaults to class name.
         */
        public Builder databaseName(String name) {
            databaseName = name;
            return this;
        }

        /**
         * extensionName
         *
         * @param name name
         * @return Pass in the extension for the DB here.
         * Otherwise defaults to ".db". If empty string passed, no extension is used.
         */
        public Builder extensionName(String name) {
            databaseExtensionName = name;
            return this;
        }

        public DatabaseConfig build() {
            return new DatabaseConfig(this);
        }
    }

    public interface OpenHelperCreator {
        OpenHelper createHelper(DBFlowDatabase db, DatabaseCallback callback);
    }

    public interface TransactionManagerCreator {
        BaseTransactionManager createManager(DBFlowDatabase db);
    }
}
